package com.shuiyun.jtt.utils;

import cn.hutool.core.codec.BCD;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.shuiyun.jtt.entity.form.bo.TachographCommand;
import com.shuiyun.jtt.entity.form.bo.TachographRequest;
import com.shuiyun.jtt.enums.TachographParam;
import com.shuiyun.jtt.message.jt808.T8700;
import com.shuiyun.jtt.message.jt808.T8701;
import com.shuiyun.jtt.utils.codec.CoderDecoderUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author shuiyun
 * @program device-simulator
 * @description 行驶记录仪命令字工具
 * @date 2022-05-12 22:32
 **/
@Slf4j
public class TachographUtil {

    final static String BCD_DATE_FORMAT = "yyMMddHHmmss";

    final static byte[] RESPONSE_HEADER = new byte[]{0x55, 0x7A};
    final static Map<TachographParam, byte[]> TACHOGRAPH_PARAM = new HashMap<>();
    private static final Map<Integer, Function<byte[], byte[]>> FUNCTION_MAP = new HashMap<>();
    private static final Map<Integer, Consumer<TachographCommand>> HANDLE_MAP = new HashMap<>();

    static {
        TACHOGRAPH_PARAM.put(TachographParam.version, new byte[]{0x03, 0x00});
        TACHOGRAPH_PARAM.put(TachographParam.driverInfo, "520723199901011234".getBytes(Charset.forName("gbk")));


        TACHOGRAPH_PARAM.put(TachographParam.vehicleInfo, new byte[]{0x41, 0x41, 0x44, 0x41, 0x41, 0x41, 0x42, 0x49, 0x41, 0x41, 0x43, 0x41, 0x41, 0x47, 0x41, 0x48, 0x41, -76, -88, 65, 49, 48, 48, 56, 54, 0, 0, 0, 0, -64, -74, -75, -41, -80, -41, -41, -42, 0, 0, 0, 0});
        TACHOGRAPH_PARAM.put(TachographParam.installationTime, new byte[]{0x22, 0x05, 0x13, 0x22, 0x30, 0x11});
        TACHOGRAPH_PARAM.put(TachographParam.statusSignal, new byte[]{-50, -27, -72, -10, -70, -70, -41, -45, -46, -69, -50, -27, -72, -10, -70, -70, -41, -45, -74, -2, -50, -27, -72, -10, -70, -70, -41, -45, -56, -3, -50, -27, -72, -10, -70, -70, -41, -45, -53, -60, -50, -27, -72, -10, -70, -70, -41, -45, -50, -27});
        TACHOGRAPH_PARAM.put(TachographParam.pulseFactor, new byte[]{0x00, 0x10});
        TACHOGRAPH_PARAM.put(TachographParam.mileage, new byte[]{0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x27, 0x10});
        TACHOGRAPH_PARAM.put(TachographParam.uniqueNumber, new byte[]{67, 67, 67, 49, 50, 51, 51, 77, 79, 68, 69, 76, 49, 50, 49, 45, 49, 76, 49, 50, 49, 45, 49, 0x22, 0x05, 0x13,  49, 48, 0, 0, 0, 0, 0, 0, 0});

        FUNCTION_MAP.put(0x00, TachographUtil::collVersion);
        FUNCTION_MAP.put(0x01, TachographUtil::driverInfo);
        FUNCTION_MAP.put(0x02, TachographUtil::realTime);
        FUNCTION_MAP.put(0x03, TachographUtil::accMileage);
        FUNCTION_MAP.put(0x04, TachographUtil::pulseCoefficient);
        FUNCTION_MAP.put(0x05, TachographUtil::vehicleInfo);
        FUNCTION_MAP.put(0x06, TachographUtil::statusInfo);
        FUNCTION_MAP.put(0x07, TachographUtil::uniqueNumber);
        FUNCTION_MAP.put(0x08, TachographUtil::speed);
        FUNCTION_MAP.put(0x09, TachographUtil::location);
        FUNCTION_MAP.put(0x10, TachographUtil::accident);
        FUNCTION_MAP.put(0x11, TachographUtil::overtimeDriving);
        FUNCTION_MAP.put(0x12, TachographUtil::driverIdCar);
        FUNCTION_MAP.put(0x13, TachographUtil::powerSupply);
        FUNCTION_MAP.put(0x14, TachographUtil::paramModify);
        FUNCTION_MAP.put(0x15, TachographUtil::speedStatus);

        HANDLE_MAP.put(0x82, TachographUtil::setVehicleInfo);
        HANDLE_MAP.put(0x83, TachographUtil::setFirstLoadTime);
        HANDLE_MAP.put(0x84, TachographUtil::setStatusInfo);
        HANDLE_MAP.put(0xc2, TachographUtil::setRealTime);
        HANDLE_MAP.put(0xc3, TachographUtil::setPulseCoefficient);
        HANDLE_MAP.put(0xc4, TachographUtil::setAccMileage);
    }


    /**
     * 数据采集处理
     *
     * @param message 请求数据
     * @return 返回数据
     * @author shuiyun
     * @date 2022/5/15 22:24
     */
    public static byte[] handle(T8700 message) {
        Function<byte[], byte[]> function = FUNCTION_MAP.get(message.getCommand());
        TachographCommand command = decode(message.getContent());
        if (ObjectUtil.isNull(function)) {
            throw new RuntimeException("未知的命令字:" + message.getCommand());
        }
        byte[] result = function.apply(command.getContent());
        return encode(message.getCommand().byteValue(), result);
    }

    /**
     * 参数设置
     * @param message 下发的消息
     * @author shuiyun
     * @date 2022/5/16 0:36
     */
    public static void handle(T8701 message) {
        Consumer<TachographCommand> function = HANDLE_MAP.get(message.getCommand());
        TachographCommand command = decode(message.getContent());
        if (ObjectUtil.isNull(function)) {
            throw new RuntimeException("未知的命令字:" + message.getCommand());
        }
        function.accept(command);
    }


    private static void setAccMileage(TachographCommand tachographCommand) {
        // 跳过实时时间6个字节 初次安装时间6个字节
        byte[] bytes = ByteUtil.skipRead(tachographCommand.getContent(), 12, 8);
        TACHOGRAPH_PARAM.put(TachographParam.mileage, bytes);
    }

    private static void setPulseCoefficient(TachographCommand tachographCommand) {
        // 跳过当前时间6个字节
        byte[] bytes = ByteUtil.skipRead(tachographCommand.getContent(), 6, 2);
        TACHOGRAPH_PARAM.put(TachographParam.pulseFactor, bytes);
    }

    private static void setRealTime(TachographCommand tachographCommand) {
        log.info("收到设置实时时间：" + BCD.bcdToStr(tachographCommand.getContent()));
    }

    private static void setStatusInfo(TachographCommand tachographCommand) {
        TACHOGRAPH_PARAM.put(TachographParam.statusSignal, tachographCommand.getContent());
    }

    private static void setFirstLoadTime(TachographCommand tachographCommand) {
        TACHOGRAPH_PARAM.put(TachographParam.installationTime, tachographCommand.getContent());
    }

    private static void setVehicleInfo(TachographCommand tachographCommand) {
        TACHOGRAPH_PARAM.put(TachographParam.vehicleInfo, tachographCommand.getContent());
    }

    private static byte[] speedStatus(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        ByteBuf resultBuf = Unpooled.buffer();
        // 返回两条速度 分别是开始和结束的时候
        int between = (int) DateUtil.between(request.getStartTime(), request.getEndTime(), DateUnit.MINUTE);
        request.setMaxNumber(Math.min(request.getMaxNumber(), between));
        byte[] speeds = ByteUtil.initBytes(120, Byte.valueOf("50", 16));
        int statIndex = 0;
        while (statIndex < request.getMaxNumber()) {
            // 速度状态 0x01 表示正常 0x02表示异常
            resultBuf.writeByte(0x01);
            // 开始时间和结束时间
            resultBuf.writeBytes(getBcd(DateUtil.offsetMinute(request.getStartTime(), statIndex)));
            resultBuf.writeBytes(getBcd(DateUtil.offsetMinute(request.getStartTime(), ++statIndex)));
            // 60秒 记录速度和参考速度都是80
            resultBuf.writeBytes(speeds);
        }

        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] paramModify(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        // 返回两条记录 查询开始时间修改了82（车辆信息） 结束时间修改了83（初次安装时间）
        ByteBuf resultBuf = Unpooled.buffer();
        // 修改82
        resultBuf.writeBytes(getBcd(request.getStartTime()));
        resultBuf.writeByte(0x82);
        // 修改0x83
        resultBuf.writeBytes(getBcd(request.getEndTime()));
        resultBuf.writeByte(0x83);

        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] powerSupply(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        // 返回两条记录 查询开始时间进行充电 查询结束时间进行断电
        ByteBuf resultBuf = Unpooled.buffer();
        // 充电
        resultBuf.writeBytes(getBcd(request.getStartTime()));
        resultBuf.writeByte(0x01);
        // 断电
        resultBuf.writeBytes(getBcd(request.getEndTime()));
        resultBuf.writeByte(0x02);

        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] driverIdCar(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        // 返回两条记录 第一条打卡 第二条签退 身份证号码都是510723199902011234
        ByteBuf resultBuf = Unpooled.buffer();
        // 签到
        resultBuf.writeBytes(getBcd(request.getEndTime()));
        resultBuf.writeBytes(new byte[]{53, 49, 48, 55, 50, 51, 49, 57, 57, 57, 48, 50, 48, 49, 49, 50, 51, 52});
        resultBuf.writeByte(0x01);
        // 签退
        resultBuf.writeBytes(getBcd(request.getEndTime()));
        resultBuf.writeBytes(new byte[]{53, 49, 48, 55, 50, 51, 49, 57, 57, 57, 48, 50, 48, 49, 49, 50, 51, 52});
        resultBuf.writeByte(0x02);


        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] overtimeDriving(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        // 这里仅返回一条记录 就是开始时间到结束时间 这段时间进行了超时驾驶
        ByteBuf resultBuf = Unpooled.buffer();
        resultBuf.writeBytes(new byte[]{53, 49, 48, 55, 50, 51, 49, 57, 57, 57, 48, 50, 48, 49, 49, 50, 51, 52});
        resultBuf.writeBytes(getBcd(request.getStartTime()));
        resultBuf.writeBytes(getBcd(request.getEndTime()));
        // 写入开始位置信息
        resultBuf.writeInt(116307629);
        resultBuf.writeInt(40058359);
        resultBuf.writeShort(460);
        // 写入结束位置信息
        resultBuf.writeInt(116307629);
        resultBuf.writeInt(40058359);
        resultBuf.writeShort(460);
        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] accident(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);

        // 获取间隔的秒数
        long between = DateUtil.between(request.getStartTime(), request.getEndTime(), DateUnit.SECOND);
        // 按照协议分20秒一组
        int apartNum = (int) (between / 20);

        request.setMaxNumber(Math.min(apartNum, request.getMaxNumber()));

        ByteBuf resultBuf = Unpooled.buffer();
        for (int j = 0; j < request.getMaxNumber(); j++) {
            resultBuf.writeBytes(getBcd(DateUtil.offsetSecond(request.getStartTime(), (j + 1) * 20)));
            // 510723199902011234 驾驶员身份证号码
            resultBuf.writeBytes(new byte[]{53, 49, 48, 55, 50, 51, 49, 57, 57, 57, 48, 50, 48, 49, 49, 50, 51, 52});
            // 0.2秒写入一次数据 20秒一共写入一百次
            for (int i = 0; i < 100; i++) {
                // 速度为80 状态为0
                resultBuf.writeBytes(new byte[]{0x50, 0x00});
            }
            // 写入位置信息
            resultBuf.writeInt(116307629);
            resultBuf.writeInt(40058359);
            resultBuf.writeShort(460);
        }

        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] location(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);
        int between = (int) DateUtil.between(request.getStartTime(), request.getEndTime(), DateUnit.HOUR);
        request.setMaxNumber(Math.min(between, request.getMaxNumber()));

        ByteBuf resultBuf = Unpooled.buffer();
        for (int i = 0; i < request.getMaxNumber(); i++) {
            // 先写入时间
            resultBuf.writeBytes(getBcd(DateUtil.offsetHour(request.getStartTime(), i)));
            for (int j = 0; j < 60; j++) {
                // 位置信息 写入 经度 纬度 海拔
                resultBuf.writeInt(116307629);
                resultBuf.writeInt(40058359);
                resultBuf.writeShort(460);
                // 速度
                resultBuf.writeByte(0x50);
            }
        }
        return ByteUtil.readReadableBytes(resultBuf);
    }

    private static byte[] speed(byte[] bytes) {
        TachographRequest request = getTachographRequest(bytes);

        int between = (int) DateUtil.between(request.getStartTime(), request.getEndTime(), DateUnit.MINUTE);
        request.setMaxNumber(Math.min(request.getMaxNumber(), between));

        byte[] temp = new byte[]{0x50, 0x00};
        ByteBuf buf = Unpooled.buffer();
        for (int i = 0; i < request.getMaxNumber(); i++) {
            byte[] tempResult = BCD.strToBcd(DateUtil.format(DateUtil.offsetMinute(request.getStartTime(), i), BCD_DATE_FORMAT));
            buf.writeBytes(tempResult);
            for (int j = 0; j < 60; j++) {
                // 速度为80 状态为0
                buf.writeBytes(temp);
            }
        }
        return ByteUtil.readReadableBytes(buf);
    }

    private static byte[] uniqueNumber(byte[] bytes) {
        return TACHOGRAPH_PARAM.get(TachographParam.uniqueNumber);
    }

    private static byte[] statusInfo(byte[] bytes) {
        // 时间
        byte[] realTime = BCD.strToBcd(DateUtil.format(new Date(), BCD_DATE_FORMAT));
        // 一个
        byte[] size = new byte[]{1};

        return ByteUtil.addBytes(realTime, size,TACHOGRAPH_PARAM.get(TachographParam.statusSignal));
    }

    private static byte[] vehicleInfo(byte[] bytes) {
        return TACHOGRAPH_PARAM.get(TachographParam.vehicleInfo);
    }

    private static byte[] pulseCoefficient(byte[] bytes) {
        // 实时时间
        byte[] realTime = BCD.strToBcd(DateUtil.format(new Date(), BCD_DATE_FORMAT));
        // 脉冲系数
        byte[] pulseFactor = TACHOGRAPH_PARAM.get(TachographParam.pulseFactor);
        return ByteUtil.addBytes(realTime, pulseFactor);
    }

    private static byte[] accMileage(byte[] bytes) {
        // 实时时间
        byte[] realTime = BCD.strToBcd(DateUtil.format(new Date(), BCD_DATE_FORMAT));
        // 初次安装时间
        byte[] installTime = TACHOGRAPH_PARAM.get(TachographParam.installationTime);
        // 初始里程 & 累计里程
        byte[] mileage = TACHOGRAPH_PARAM.get(TachographParam.mileage);
        return ByteUtil.addBytes(realTime, installTime, mileage);
    }

    private static byte[] realTime(byte[] bytes) {
        return BCD.strToBcd(DateUtil.format(new Date(), BCD_DATE_FORMAT));
    }

    private static byte[] driverInfo(byte[] bytes) {
        return TACHOGRAPH_PARAM.get(TachographParam.driverInfo);
    }

    /**
     * 行驶记录仪版本
     *
     * @param bytes 数据块
     * @return 应答帧
     * @author shuiyun
     * @date 2022/5/13 22:58
     */
    private static byte[] collVersion(byte[] bytes) {
        return TACHOGRAPH_PARAM.get(TachographParam.version);
    }

    /**
     * 数据解码工具
     *
     * @param content 待解码数据
     * @return 解码之后的命令帧数据
     * @author shuiyun
     * @date 2022/5/12 22:54
     */
    private static TachographCommand decode(byte[] content) {
        ByteBuf buf = Unpooled.wrappedBuffer(content);
        buf = buf.slice(0, buf.readableBytes() - 1);
        byte bcc = ByteUtil.bcc(buf);
        if (bcc != content[content.length - 1]) {
            throw new RuntimeException("校验字不通过");
        }
        // 跳过头的读取 固定为 0xAA 0x75
        buf.skipBytes(2);
        // 命令字
        int key = buf.readUnsignedByte();
        // 数据长度
        int length = buf.readUnsignedShort();
        // 跳过备用字
        buf.skipBytes(1);
        byte[] bytes = new byte[length];
        buf.readBytes(bytes);
        return new TachographCommand(key, bytes);
    }

    /**
     * 数据编码
     *
     * @param key     命令字
     * @param content 数据体
     * @return 编码好的数据
     * @author shuiyun
     * @date 2022/5/12 23:07
     */
    private static byte[] encode(byte key, byte[] content) {
        ByteBuf buf = Unpooled.buffer();
        buf.writeBytes(RESPONSE_HEADER);
        // 命令字
        buf.writeByte(key);
        // 数据块长度
        buf.writeShort(content.length);
        // 备用字
        buf.writeByte(0x00);
        buf.writeBytes(content);
        byte bcc = ByteUtil.bcc(buf);
        buf.writeByte(bcc);
        return ByteUtil.readReadableBytes(buf);
    }

    /**
     * 读取BCD 数据 转换为时间
     *
     * @param buf 字节缓存
     * @return 转换好的时间
     * @author shuiyun
     * @date 2022/5/14 16:54
     */
    private static Date getTime(ByteBuf buf) {
        byte[] bytes = new byte[6];
        buf.writeBytes(bytes);
        String s = BCD.bcdToStr(bytes);
        return DateUtil.parse(s, BCD_DATE_FORMAT);
    }

    /**
     * 时间转bcd数据格式
     *
     * @param date 待转换的时间
     * @return bcd数据
     * @author shuiyun
     * @date 2022/5/14 20:24
     */
    private static byte[] getBcd(Date date) {
        String format = DateUtil.format(date, BCD_DATE_FORMAT);
        return BCD.strToBcd(format);
    }

    /**
     * 请求数据转请求参数
     *
     * @param bytes 请求数据
     * @return 转换好的请求参数
     * @author shuiyun
     * @date 2022/5/14 22:36
     */
    private static TachographRequest getTachographRequest(byte[] bytes) {
        ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        TachographRequest request = CoderDecoderUtil.byteBuf2Obj(buf, TachographRequest.class);
        buf.release();
        return request;
    }

}
